/****************************************************************************
 *
 * Copyright 2018 Samsung Electronics All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 ****************************************************************************/
/****************************************************************************
 * fs/tmpfs/fs_tmpfs.c
 *
 *   Copyright (C) 2015, 2017 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>

#include <sys/stat.h>
#include <sys/statfs.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <semaphore.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <unistd.h>

#include <tinyara/kmalloc.h>
#include <tinyara/fs/fs.h>
#include <tinyara/fs/dirent.h>
#include <tinyara/fs/ioctl.h>

#include "fs_tmpfs.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#if CONFIG_FS_TMPFS_DIRECTORY_FREEGUARD <= CONFIG_FS_TMPFS_DIRECTORY_ALLOCGUARD
#  warning CONFIG_FS_TMPFS_DIRECTORY_FREEGUARD needs to be > ALLOCGUARD
#endif

#if CONFIG_FS_TMPFS_FILE_FREEGUARD <= CONFIG_FS_TMPFS_FILE_ALLOCGUARD
#  warning CONFIG_FS_TMPFS_FILE_FREEGUARD needs to be > ALLOCGUARD
#endif

#define tmpfs_lock_file(tfo) \
	(tmpfs_lock_object((FAR struct tmpfs_object_s *)tfo))
#define tmpfs_lock_directory(tdo) \
	(tmpfs_lock_object((FAR struct tmpfs_object_s *)tdo))
#define tmpfs_unlock_file(tfo) \
	(tmpfs_unlock_object((FAR struct tmpfs_object_s *)tfo))
#define tmpfs_unlock_directory(tdo) \
	(tmpfs_unlock_object((FAR struct tmpfs_object_s *)tdo))

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/
/* TMPFS helpers */

static void tmpfs_lock_reentrant(FAR struct tmpfs_sem_s *sem);
static void tmpfs_lock(FAR struct tmpfs_s *fs);
static void tmpfs_unlock_reentrant(FAR struct tmpfs_sem_s *sem);
static void tmpfs_unlock(FAR struct tmpfs_s *fs);
static void tmpfs_lock_object(FAR struct tmpfs_object_s *to);
static void tmpfs_unlock_object(FAR struct tmpfs_object_s *to);
static int tmpfs_realloc_directory(FAR struct tmpfs_directory_s **tdo, unsigned int nentries);
static int tmpfs_realloc_file(FAR struct tmpfs_file_s **tfo, size_t newsize);
static void tmpfs_release_lockedobject(FAR struct tmpfs_object_s *to);
static void tmpfs_release_lockedfile(FAR struct tmpfs_file_s *tfo);
static int tmpfs_find_dirent(FAR struct tmpfs_directory_s *tdo, FAR const char *name);
static int tmpfs_remove_dirent(FAR struct tmpfs_directory_s *tdo, FAR const char *name);
static int tmpfs_add_dirent(FAR struct tmpfs_directory_s **tdo, FAR struct tmpfs_object_s *to, FAR const char *name);
static FAR struct tmpfs_file_s *tmpfs_alloc_file(void);
static int tmpfs_create_file(FAR struct tmpfs_s *fs,	FAR const char *relpath, FAR struct tmpfs_file_s **tfo);
static FAR struct tmpfs_directory_s *tmpfs_alloc_directory(void);
static int tmpfs_create_directory(FAR struct tmpfs_s *fs, FAR const char *relpath, FAR struct tmpfs_directory_s **tdo);
static int tmpfs_find_object(FAR struct tmpfs_s *fs, FAR const char *relpath, FAR struct tmpfs_object_s **object, FAR struct tmpfs_directory_s **parent);
static int tmpfs_find_file(FAR struct tmpfs_s *fs, FAR const char *relpath, FAR struct tmpfs_file_s **tfo, FAR struct tmpfs_directory_s **parent);
static int tmpfs_find_directory(FAR struct tmpfs_s *fs, FAR const char *relpath, FAR struct tmpfs_directory_s **tdo, FAR struct tmpfs_directory_s **parent);
static int tmpfs_statfs_callout(FAR struct tmpfs_directory_s *tdo, unsigned int index, FAR void *arg);
static int tmpfs_free_callout(FAR struct tmpfs_directory_s *tdo, unsigned int index, FAR void *arg);
static int tmpfs_foreach(FAR struct tmpfs_directory_s *tdo, tmpfs_foreach_t callout, FAR void *arg);

/* File system operations */

static int tmpfs_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode);
static int tmpfs_close(FAR struct file *filep);
static ssize_t tmpfs_read(FAR struct file *filep, FAR char *buffer, size_t buflen);
static ssize_t tmpfs_write(FAR struct file *filep, FAR const char *buffer, size_t buflen);
static off_t tmpfs_seek(FAR struct file *filep, off_t offset, int whence);
static int tmpfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
static int tmpfs_dup(FAR const struct file *oldp, FAR struct file *newp);
static int tmpfs_fstat(FAR const struct file *filep, FAR struct stat *buf);
static int tmpfs_truncate(FAR struct file *filep, off_t length);
static int tmpfs_opendir(FAR struct inode *mountpt, FAR const char *relpath, FAR struct fs_dirent_s *dir);
static int tmpfs_closedir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir);
static int tmpfs_readdir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir);
static int tmpfs_rewinddir(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir);
static int tmpfs_bind(FAR struct inode *blkdriver, FAR const void *data, FAR void **handle);
static int tmpfs_unbind(FAR void *handle, FAR struct inode **blkdriver);
static int tmpfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf);
static int tmpfs_unlink(FAR struct inode *mountpt, FAR const char *relpath);
static int tmpfs_mkdir(FAR struct inode *mountpt, FAR const char *relpath, mode_t mode);
static int tmpfs_rmdir(FAR struct inode *mountpt, FAR const char *relpath);
static int tmpfs_rename(FAR struct inode *mountpt, FAR const char *oldrelpath, FAR const char *newrelpath);
static void tmpfs_stat_common(FAR struct tmpfs_object_s *to, FAR struct stat *buf);
static int tmpfs_stat(FAR struct inode *mountpt, FAR const char *relpath, FAR struct stat *buf);

/****************************************************************************
 * Public Data
 ****************************************************************************/

const struct mountpt_operations tmpfs_operations = {
	tmpfs_open,       /* open */
	tmpfs_close,      /* close */
	tmpfs_read,       /* read */
	tmpfs_write,      /* write */
	tmpfs_seek,       /* seek */
	tmpfs_ioctl,      /* ioctl */
	
	NULL,             /* sync */
	tmpfs_dup,        /* dup */
	tmpfs_fstat,      /* fstat */
	tmpfs_truncate,   /* truncate */

	tmpfs_opendir,    /* opendir */
	tmpfs_closedir,   /* closedir */
	tmpfs_readdir,    /* readdir */
	tmpfs_rewinddir,  /* rewinddir */

	tmpfs_bind,       /* bind */
	tmpfs_unbind,     /* unbind */
	tmpfs_statfs,     /* statfs */
	tmpfs_unlink,     /* unlink */
	tmpfs_mkdir,      /* mkdir */
	tmpfs_rmdir,      /* rmdir */
	tmpfs_rename,     /* rename */
	tmpfs_stat,       /* stat */
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: tmpfs_lock_reentrant
 ****************************************************************************/

static void tmpfs_lock_reentrant(FAR struct tmpfs_sem_s *sem)
{
	pid_t me;

	/* Do we already hold the semaphore? */

	me = getpid();
	if (me == sem->ts_holder) {
		/* Yes... just increment the count */

		sem->ts_count++;
		DEBUGASSERT(sem->ts_count > 0);
	}

	/* Take the semaphore (perhaps waiting) */

	else {
		while (sem_wait(&sem->ts_sem) != 0) {
			/* The only case that an error should occur here is if
			 * the wait was awakened by a signal.
			 */

			ASSERT(get_errno() == EINTR);
		}

		/* No we hold the semaphore */

		sem->ts_holder = me;
		sem->ts_count  = 1;
	}
}

/****************************************************************************
 * Name: tmpfs_lock
 ****************************************************************************/

static void tmpfs_lock(FAR struct tmpfs_s *fs)
{
	tmpfs_lock_reentrant(&fs->tfs_exclsem);
}

/****************************************************************************
 * Name: tmpfs_lock_object
 ****************************************************************************/

static void tmpfs_lock_object(FAR struct tmpfs_object_s *to)
{
	tmpfs_lock_reentrant(&to->to_exclsem);
}

/****************************************************************************
 * Name: tmpfs_unlock_reentrant
 ****************************************************************************/

static void tmpfs_unlock_reentrant(FAR struct tmpfs_sem_s *sem)
{
	DEBUGASSERT(sem->ts_holder == getpid());

	/* Is this our last count on the semaphore? */

	if (sem->ts_count > 1) {
		/* No.. just decrement the count */

		sem->ts_count--;
	}

	/* Yes.. then we can really release the semaphore */

	else {
		sem->ts_holder = TMPFS_NO_HOLDER;
		sem->ts_count  = 0;
		sem_post(&sem->ts_sem);
	}
}

/****************************************************************************
 * Name: tmpfs_unlock
 ****************************************************************************/

static void tmpfs_unlock(FAR struct tmpfs_s *fs)
{
	tmpfs_unlock_reentrant(&fs->tfs_exclsem);
}

/****************************************************************************
 * Name: tmpfs_unlock_object
 ****************************************************************************/

static void tmpfs_unlock_object(FAR struct tmpfs_object_s *to)
{
	tmpfs_unlock_reentrant(&to->to_exclsem);
}

/****************************************************************************
 * Name: tmpfs_realloc_directory
 ****************************************************************************/

static int tmpfs_realloc_directory(FAR struct tmpfs_directory_s **tdo,
		unsigned int nentries)
{
	FAR struct tmpfs_directory_s *oldtdo = *tdo;
	FAR struct tmpfs_directory_s *newtdo;
	size_t objsize;
	int ret = oldtdo->tdo_nentries;

	/* Get the new object size */

	objsize = SIZEOF_TMPFS_DIRECTORY(nentries);
	if (objsize <= oldtdo->tdo_alloc) {
		/* Already big enough.
		 * REVISIT: Missing logic to shrink directory objects.
		 */

		oldtdo->tdo_nentries = nentries;
		return ret;
	}

	/* Added some additional amount to the new size to account frequent
	 * reallocations.
	 */

	objsize += CONFIG_FS_TMPFS_DIRECTORY_ALLOCGUARD;

	/* Realloc the directory object */

	newtdo = (FAR struct tmpfs_directory_s *)kmm_realloc(oldtdo, objsize);
	if (newtdo == NULL) {
		return -ENOMEM;
	}
	/* Adjust the reference in the parent directory entry */

	DEBUGASSERT(newtdo->tdo_dirent);
	newtdo->tdo_dirent->tde_object = (FAR struct tmpfs_object_s *)newtdo;

	/* Return the new address of the reallocated directory object */

	newtdo->tdo_alloc    = objsize;
	newtdo->tdo_nentries = nentries;
	*tdo                 = newtdo;

	/* Adjust the reference in the parent directory entry */

	DEBUGASSERT(newtdo->tdo_dirent);
	newtdo->tdo_dirent->tde_object = (FAR struct tmpfs_object_s *)newtdo;

	/* Return the index to the first, newly allocated directory entry */

	return ret;
}

/****************************************************************************
 * Name: tmpfs_realloc_file
 ****************************************************************************/

static int tmpfs_realloc_file(FAR struct tmpfs_file_s **tfo,
		size_t newsize)
{
	FAR struct tmpfs_file_s *oldtfo = *tfo;
	FAR struct tmpfs_file_s *newtfo;
	size_t objsize;
	size_t allocsize;

	/* Check if the current allocation is sufficent */

	objsize = SIZEOF_TMPFS_FILE(newsize);

	/* Are we growing or shrinking the object? */

	if (objsize <= oldtfo->tfo_alloc) {
		/* Shrinking ... Shrink unconditionally if the size is shrinking to
		 * zero.
		 */

		if (newsize > 0) {
			/* Otherwise, don't realloc unless the object has shrunk by a
			 * lot.
			 */

			if (oldtfo->tfo_alloc - objsize <= CONFIG_FS_TMPFS_FILE_FREEGUARD) {
				/* Hasn't shrunk enough.. Return doing nothing for now */

				oldtfo->tfo_size = newsize;
				return OK;
			}
		}
	}

	/* Added some additional amount to the new size to account frequent
	 * reallocations.
	 */

	allocsize = objsize + CONFIG_FS_TMPFS_FILE_ALLOCGUARD;

	/* Realloc the file object */

	newtfo = (FAR struct tmpfs_file_s *)kmm_realloc(oldtfo, allocsize);
	if (newtfo == NULL) {
		return -ENOMEM;
	}
	/* Adjust the reference in the parent directory entry */

	DEBUGASSERT(newtfo->tfo_dirent);
	newtfo->tfo_dirent->tde_object = (FAR struct tmpfs_object_s *)newtfo;

	/* Return the new address of the reallocated file object */

	newtfo->tfo_alloc = allocsize;
	newtfo->tfo_size  = newsize;
	*tfo              = newtfo;
	return OK;
}

/****************************************************************************
 * Name: tmpfs_release_lockedobject
 ****************************************************************************/

static void tmpfs_release_lockedobject(FAR struct tmpfs_object_s *to)
{
	DEBUGASSERT(to && to->to_refs > 0);

	/* Is this a file object? */

	if (to->to_type == TMPFS_REGULAR) {
		tmpfs_release_lockedfile((FAR struct tmpfs_file_s *)to);
	} else {
		to->to_refs--;
		tmpfs_unlock_object(to);
	}
}

/****************************************************************************
 * Name: tmpfs_release_lockedfile
 ****************************************************************************/

static void tmpfs_release_lockedfile(FAR struct tmpfs_file_s *tfo)
{
	DEBUGASSERT(tfo && tfo->tfo_refs > 0);

	/* If there are no longer any references to the file and the file has been
	 * unlinked from its parent directory, then free the file object now.
	 */

	if (tfo->tfo_refs == 1 && (tfo->tfo_flags & TFO_FLAG_UNLINKED) != 0) {
		sem_destroy(&tfo->tfo_exclsem.ts_sem);
		kmm_free(tfo);
	}

	/* Otherwise, just decrement the reference count on the file object */

	else {
		tfo->tfo_refs--;
		tmpfs_unlock_file(tfo);
	}
}

/****************************************************************************
 * Name: tmpfs_find_dirent
 ****************************************************************************/

static int tmpfs_find_dirent(FAR struct tmpfs_directory_s *tdo,
		FAR const char *name)
{
	int i;

	/* Search the list of directory entries for a match */

	for (i = 0;
			i < tdo->tdo_nentries &&
			strcmp(tdo->tdo_entry[i].tde_name, name) != 0;
			i++);

	/* Return what we found, if anything */

	return i < tdo->tdo_nentries ? i : -ENOENT;
}

/****************************************************************************
 * Name: tmpfs_remove_dirent
 ****************************************************************************/

static int tmpfs_remove_dirent(FAR struct tmpfs_directory_s *tdo,
		FAR const char *name)
{
	int index;
	int last;

	/* Search the list of directory entries for a match */

	index = tmpfs_find_dirent(tdo, name);
	if (index < 0) {
		return index;
	}
	/* Free the object name */

	if (tdo->tdo_entry[index].tde_name != NULL) {
		kmm_free(tdo->tdo_entry[index].tde_name);
	}
	/* Remove by replacing this entry with the final directory entry */

	last = tdo->tdo_nentries - 1;
	if (index != last) {
		FAR struct tmpfs_dirent_s *newtde;
		FAR struct tmpfs_dirent_s *oldtde;
		FAR struct tmpfs_object_s *to;

		/* Move the directory entry */

		newtde             = &tdo->tdo_entry[index];
		oldtde             = &tdo->tdo_entry[last];
		to                 = oldtde->tde_object;

		newtde->tde_object = to;
		newtde->tde_name   = oldtde->tde_name;

		/* Reset the backward link to the directory entry */

		to->to_dirent      = newtde;
	}

	/* And decrement the count of directory entries */

	tdo->tdo_nentries = last;
	return OK;
}

/****************************************************************************
 * Name: tmpfs_add_dirent
 ****************************************************************************/

static int tmpfs_add_dirent(FAR struct tmpfs_directory_s **tdo,
		FAR struct tmpfs_object_s *to,
		FAR const char *name)
{
	FAR struct tmpfs_directory_s *oldtdo;
	FAR struct tmpfs_directory_s *newtdo;
	FAR struct tmpfs_dirent_s *tde;
	FAR char *newname;
	unsigned int nentries;
	int index;

	/* Copy the name string so that it will persist as long as the
	 * directory entry.
	 */

	newname = strdup(name);
	if (newname == NULL) {
		return -ENOMEM;
	}
	/* Get the new number of entries */

	oldtdo = *tdo;
	nentries = oldtdo->tdo_nentries + 1;

	/* Reallocate the directory object (if necessary) */

	index = tmpfs_realloc_directory(tdo, nentries);
	if (index < 0) {
		kmm_free(newname);
		return index;
	}

	/* Save the new object info in the new directory entry */

	newtdo          = *tdo;
	tde             = &newtdo->tdo_entry[index];
	tde->tde_object = to;
	tde->tde_name   = newname;

	/* Add backward link to the directory entry to the object */

	to->to_dirent  = tde;
	return OK;
}

/****************************************************************************
 * Name: tmpfs_alloc_file
 ****************************************************************************/

static FAR struct tmpfs_file_s *tmpfs_alloc_file(void)
{
	FAR struct tmpfs_file_s *tfo;
	size_t allocsize;

	/* Create a new zero length file object */

	allocsize = SIZEOF_TMPFS_FILE(CONFIG_FS_TMPFS_FILE_ALLOCGUARD);
	tfo = (FAR struct tmpfs_file_s *)kmm_malloc(allocsize);
	if (tfo == NULL) {
		return NULL;
	}
	/* Initialize the new file object.  NOTE that the initial state is
	 * locked with one reference count.
	 */

	tfo->tfo_alloc = allocsize;
	tfo->tfo_type  = TMPFS_REGULAR;
	tfo->tfo_refs  = 1;
	tfo->tfo_flags = 0;
	tfo->tfo_size  = 0;

	tfo->tfo_exclsem.ts_holder = getpid();
	tfo->tfo_exclsem.ts_count  = 1;
	sem_init(&tfo->tfo_exclsem.ts_sem, 0, 0);

	return tfo;
}

/****************************************************************************
 * Name: tmpfs_create_file
 ****************************************************************************/

static int tmpfs_create_file(FAR struct tmpfs_s *fs,
		FAR const char *relpath,
		FAR struct tmpfs_file_s **tfo)
{
	FAR struct tmpfs_directory_s *parent;
	FAR struct tmpfs_file_s *newtfo;
	FAR char *copy;
	FAR char *name;
	int ret;

	/* Duplicate the path variable so that we can modify it */

	copy = strdup(relpath);
	if (copy == NULL) {
		return -ENOMEM;
	}

	/* Separate the path into the file name and the path to the parent
	 * directory.
	 */

	name = strrchr(copy, '/');
	if (name == NULL) {
		/* No subdirectories... use the root directory */

		name   = copy;
		parent = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;

		/* Lock the root directory to emulate the behavior of tmpfs_find_directory() */

		tmpfs_lock_directory(parent);
		parent->tdo_refs++;
	} else {
		/* Terminate the parent directory path */

		*name++ = '\0';

		/* Locate the parent directory that should contain this name.
		 * On success, tmpfs_find_directory() will lock the parent
		 * directory and increment the reference count.
		 */

		ret = tmpfs_find_directory(fs, copy, &parent, NULL);
		if (ret < 0) {
			goto errout_with_copy;
		}
	}

	/* Verify that no object of this name already exists in the directory */

	ret = tmpfs_find_dirent(parent, name);
	if (ret != -ENOENT) {
		/* Something with this name already exists in the directory.
		 * OR perhaps some fatal error occurred.
		 */

		if (ret >= 0) {
			ret	= -EEXIST;
		}
		goto errout_with_parent;
	}

	/* Allocate an empty file.  The initial state of the file is locked with one
	 * reference count.
	 */

	newtfo = tmpfs_alloc_file();
	if (newtfo == NULL) {
		ret = -ENOMEM;
		goto errout_with_parent;
	}

	/* Then add the new, empty file to the directory */

	ret = tmpfs_add_dirent(&parent, (FAR struct tmpfs_object_s *)newtfo, name);
	if (ret < 0) {
		goto errout_with_file;
	}
	/* Release the reference and lock on the parent directory */

	parent->tdo_refs--;
	tmpfs_unlock_directory(parent);

	/* Free the copy of the relpath and return success */

	kmm_free(copy);
	*tfo = newtfo;
	return OK;

	/* Error exits */

errout_with_file:
	sem_destroy(&newtfo->tfo_exclsem.ts_sem);
	kmm_free(newtfo);

errout_with_parent:
	parent->tdo_refs--;
	tmpfs_unlock_directory(parent);

errout_with_copy:
	kmm_free(copy);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_alloc_directory
 ****************************************************************************/

static FAR struct tmpfs_directory_s *tmpfs_alloc_directory(void)
{
	FAR struct tmpfs_directory_s *tdo;
	size_t allocsize;
	unsigned int nentries;

	/* Convert the pre-allocated memory to a number of directory entries */

	nentries = (CONFIG_FS_TMPFS_DIRECTORY_ALLOCGUARD +
			sizeof(struct tmpfs_dirent_s) - 1) /
		sizeof(struct tmpfs_dirent_s);

	/* Create a new zero length directory object */

	allocsize = SIZEOF_TMPFS_DIRECTORY(nentries);
	tdo = (FAR struct tmpfs_directory_s *)kmm_malloc(allocsize);
	if (tdo == NULL) {
		return NULL;
	}
	/* Initialize the new directory object */

	tdo->tdo_alloc    = allocsize;
	tdo->tdo_type     = TMPFS_DIRECTORY;
	tdo->tdo_refs     = 0;
	tdo->tdo_nentries = 0;

	tdo->tdo_exclsem.ts_holder = TMPFS_NO_HOLDER;
	tdo->tdo_exclsem.ts_count  = 0;
	sem_init(&tdo->tdo_exclsem.ts_sem, 0, 1);

	return tdo;
}

/****************************************************************************
 * Name: tmpfs_create_directory
 ****************************************************************************/

static int tmpfs_create_directory(FAR struct tmpfs_s *fs,
		FAR const char *relpath,
		FAR struct tmpfs_directory_s **tdo)
{
	FAR struct tmpfs_directory_s *parent;
	FAR struct tmpfs_directory_s *newtdo;
	FAR char *copy;
	FAR char *name;
	int ret;

	/* Duplicate the path variable so that we can modify it */

	copy = strdup(relpath);
	if (copy == NULL) {
		return -ENOMEM;
	}
	/* Separate the path into the file name and the path to the parent
	 * directory.
	 */

	name = strrchr(copy, '/');
	if (name == NULL) {
		/* No subdirectories... use the root directory */

		name   = copy;
		parent = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;

		tmpfs_lock_directory(parent);
		parent->tdo_refs++;
	} else {
		/* Terminate the parent directory path */

		*name++ = '\0';

		/* Locate the parent directory that should contain this name.
		 * On success, tmpfs_find_directory() will lockthe parent
		 * directory and increment the reference count.
		 */

		ret = tmpfs_find_directory(fs, copy, &parent, NULL);
		if (ret < 0) {
			goto errout_with_copy;
		}
	}

	/* Verify that no object of this name already exists in the directory */

	ret = tmpfs_find_dirent(parent, name);
	if (ret != -ENOENT) {
		/* Something with this name already exists in the directory.
		 * OR perhaps some fatal error occurred.
		 */

		if (ret >= 0) {
			ret = -EEXIST;
		}
		goto errout_with_parent;
	}

	/* Allocate an empty directory object.  NOTE that there is no reference on
	 * the new directory and the object is not locked.
	 */

	newtdo = tmpfs_alloc_directory();
	if (newtdo == NULL) {
		ret = -ENOMEM;
		goto errout_with_parent;
	}

	/* Then add the new, empty file to the directory */

	ret = tmpfs_add_dirent(&parent, (FAR struct tmpfs_object_s *)newtdo, name);
	if (ret < 0) {
		goto errout_with_directory;
	}
	/* Free the copy of the relpath, release our reference to the parent directory,
	 * and return success
	 */

	parent->tdo_refs--;
	tmpfs_unlock_directory(parent);
	kmm_free(copy);

	/* Return the (unlocked, unreferenced) directory object to the caller */

	if (tdo != NULL) {
		*tdo = newtdo;
	}
	return OK;

	/* Error exits */

errout_with_directory:
	sem_destroy(&newtdo->tdo_exclsem.ts_sem);
	kmm_free(newtdo);

errout_with_parent:
	parent->tdo_refs--;
	tmpfs_unlock_directory(parent);

errout_with_copy:
	kmm_free(copy);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_find_object
 ****************************************************************************/

static int tmpfs_find_object(FAR struct tmpfs_s *fs,
		FAR const char *relpath,
		FAR struct tmpfs_object_s **object,
		FAR struct tmpfs_directory_s **parent)
{
	FAR struct tmpfs_object_s *to = NULL;
	FAR struct tmpfs_directory_s *tdo = NULL;
	FAR struct tmpfs_directory_s *next_tdo;
	FAR char *segment;
	FAR char *next_segment;
	FAR char *tkptr;
	FAR char *copy;
	int index;

	/* Make a copy of the path (so that we can modify it via strtok) */

	copy = strdup(relpath);
	if (copy == NULL) {
		return -ENOMEM;
	}
	/* Traverse the file system for any object with the matching name */

	to = fs->tfs_root.tde_object;
	next_tdo = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;

	for (segment =  strtok_r(copy, "/", &tkptr);
			segment != NULL;
			segment = next_segment) {
		/* Get the next segment after the one we are currently working on.
		 * This will be NULL is we are working on the final segment of the
		 * relpath.
		 */

		next_segment = strtok_r(NULL, "/", &tkptr);

		/* Search the next directory. */

		tdo = next_tdo;

		/* Find the TMPFS object with the next segment name in the current
		 * directory.
		 */

		index = tmpfs_find_dirent(tdo, segment);
		if (index < 0) {
			/* No object with this name exists in the directory. */

			kmm_free(copy);
			return index;
		}

		to = tdo->tdo_entry[index].tde_object;

		/* Is this object another directory? */

		if (to->to_type != TMPFS_DIRECTORY) {
			/* No.  Was this the final segment in the path? */

			if (next_segment == NULL) {
				/* Then we can break out of the loop now */

				break;
			}

			/* No, this was not the final segement of the relpath.
			 * We cannot continue the search if any of the intermediate
			 * segments do no correspond to directories.
			 */

			kmm_free(copy);
			return -ENOTDIR;
		}

		/* Search this directory for the next segement.  If we
		 * exit the loop, tdo will still refer to the parent
		 * directory of to.
		 */

		next_tdo = (FAR struct tmpfs_directory_s *)to;
	}

	/* When we exit this loop (successfully), to will point to the TMPFS
	 * object associated with the terminal segment of the relpath.
	 * Increment the reference count on the located object.
	 */

	/* Free the dup'ed string */

	kmm_free(copy);

	/* Return what we found */

	if (parent) {
		if (tdo != NULL) {
			/* Get exclusive access to the parent and increment the reference
			 * count on the object.
			 */

			tmpfs_lock_directory(tdo);
			tdo->tdo_refs++;
		}

		*parent = tdo;
	}

	if (object) {
		if (to != NULL) {
			/* Get exclusive access to the object and increment the reference
			 * count on the object.
			 */

			tmpfs_lock_object(to);
			to->to_refs++;
		}

		*object = to;
	}

	return OK;
}

/****************************************************************************
 * Name: tmpfs_find_file
 ****************************************************************************/

static int tmpfs_find_file(FAR struct tmpfs_s *fs,
		FAR const char *relpath,
		FAR struct tmpfs_file_s **tfo,
		FAR struct tmpfs_directory_s **parent)
{
	FAR struct tmpfs_object_s *to;
	int ret;

	/* Find the object at this path.  If successful, tmpfs_find_object() will
	 * lock both the object and the parent directory and will increment the
	 * reference count on both.
	 */

	ret = tmpfs_find_object(fs, relpath, &to, parent);
	if (ret >= 0) {
		/* We found it... but is it a regular file? */

		if (to->to_type != TMPFS_REGULAR) {
			/* No... unlock the object and its parent and return an error */

			tmpfs_release_lockedobject(to);

			if (parent) {
				FAR struct tmpfs_directory_s *tdo = *parent;

				tdo->tdo_refs--;
				tmpfs_unlock_directory(tdo);
			}

			ret = -EISDIR;
		}

		/* Return the verified file object */

		*tfo = (FAR struct tmpfs_file_s *)to;
	}

	return ret;
}

/****************************************************************************
 * Name: tmpfs_find_directory
 ****************************************************************************/

static int tmpfs_find_directory(FAR struct tmpfs_s *fs,
		FAR const char *relpath,
		FAR struct tmpfs_directory_s **tdo,
		FAR struct tmpfs_directory_s **parent)
{
	FAR struct tmpfs_object_s *to;
	int ret;

	/* Find the object at this path */

	ret = tmpfs_find_object(fs, relpath, &to, parent);
	if (ret >= 0) {
		/* We found it... but is it a regular file? */

		if (to->to_type != TMPFS_DIRECTORY) {
			/* No... unlock the object and its parent and return an error */

			tmpfs_release_lockedobject(to);

			if (parent) {
				FAR struct tmpfs_directory_s *tmptdo = *parent;

				tmptdo->tdo_refs--;
				tmpfs_unlock_directory(tmptdo);
			}

			ret = -ENOTDIR;
		}

		/* Return the verified file object */

		*tdo = (FAR struct tmpfs_directory_s *)to;
	}

	return ret;
}

/****************************************************************************
 * Name: tmpfs_statfs_callout
 ****************************************************************************/

static int tmpfs_statfs_callout(FAR struct tmpfs_directory_s *tdo,
		unsigned int index, FAR void *arg)
{
	FAR struct tmpfs_object_s *to;
	FAR struct tmpfs_statfs_s *tmpbuf;

	DEBUGASSERT(tdo != NULL && arg != NULL && index < tdo->tdo_nentries);

	to     = tdo->tdo_entry[index].tde_object;
	tmpbuf = (FAR struct tmpfs_statfs_s *)arg;

	DEBUGASSERT(to != NULL);

	/* Accumulate statistics.  Save the total memory allocted for this object. */

	tmpbuf->tsf_alloc += to->to_alloc;

	/* Is this directory entry a file object? */

	if (to->to_type == TMPFS_REGULAR) {
		FAR struct tmpfs_file_s *tmptfo;

		/* It is a file object.  Increment the number of files and update the
		 * amount of memory in use.
		 */

		tmptfo = (FAR struct tmpfs_file_s *)to;
		tmpbuf->tsf_inuse += tmptfo->tfo_size;
		tmpbuf->tsf_files++;
	} else /* if (to->to_type == TMPFS_DIRECTORY) */ {
		FAR struct tmpfs_directory_s *tmptdo;
		size_t inuse;
		size_t avail;

		/* It is a directory object.  Update the amount of memory in use
		 * for the directory and estimate the number of free directory nodes.
		 */

		tmptdo = (FAR struct tmpfs_directory_s *)to;
		inuse  = SIZEOF_TMPFS_DIRECTORY(tmptdo->tdo_nentries);
		avail  = tmptdo->tdo_alloc - inuse;

		tmpbuf->tsf_inuse += inuse;
		tmpbuf->tsf_ffree += avail / sizeof(struct tmpfs_dirent_s);
	}

	return TMPFS_CONTINUE;
}

/****************************************************************************
 * Name: tmpfs_free_callout
 ****************************************************************************/

static int tmpfs_free_callout(FAR struct tmpfs_directory_s *tdo,
		unsigned int index, FAR void *arg)
{
	FAR struct tmpfs_dirent_s *tde;
	FAR struct tmpfs_object_s *to;
	FAR struct tmpfs_file_s *tfo;
	unsigned int last;

	/* Free the object name */

	if (tdo->tdo_entry[index].tde_name != NULL) {
		kmm_free(tdo->tdo_entry[index].tde_name);
	}
	/* Remove by replacing this entry with the final directory entry */

	tde  = &tdo->tdo_entry[index];
	to   = tde->tde_object;
	last = tdo->tdo_nentries - 1;

	if (index != last) {
		FAR struct tmpfs_dirent_s *oldtde;
		FAR struct tmpfs_object_s *oldto;

		/* Move the directory entry */

		oldtde           = &tdo->tdo_entry[last];
		oldto            = oldtde->tde_object;

		tde->tde_object  = oldto;
		tde->tde_name    = oldtde->tde_name;

		/* Reset the backward link to the directory entry */

		oldto->to_dirent = tde;
	}

	/* And decrement the count of directory entries */

	tdo->tdo_nentries = last;

	/* Is this directory entry a file object? */

	if (to->to_type == TMPFS_REGULAR) {
		tfo = (FAR struct tmpfs_file_s *)to;

		/* Are there references to the file? */

		if (tfo->tfo_refs > 0) {
			/* Yes.. We cannot delete the file now.  Just mark it as unlinked. */

			tfo->tfo_flags |= TFO_FLAG_UNLINKED;
			return TMPFS_UNLINKED;
		}
	}

	/* Free the object now */

	sem_destroy(&to->to_exclsem.ts_sem);
	kmm_free(to);
	return TMPFS_DELETED;
}

/****************************************************************************
 * Name: tmpfs_foreach
 ****************************************************************************/

static int tmpfs_foreach(FAR struct tmpfs_directory_s *tdo,
		tmpfs_foreach_t callout, FAR void *arg)
{
	FAR struct tmpfs_object_s *to;
	unsigned int index;
	int ret;

	/* Visit each directory entry */

	for (index = 0; index < tdo->tdo_nentries; ) {
		/* Lock the object and take a reference */

		to = tdo->tdo_entry[index].tde_object;
		tmpfs_lock_object(to);
		to->to_refs++;

		/* Is the next entry a directory? */

		if (to->to_type == TMPFS_DIRECTORY) {
			FAR struct tmpfs_directory_s *next =
				(FAR struct tmpfs_directory_s *)to;

			/* Yes.. traverse its children first in the case the final
			 * action will be to delete the directory.
			 */

			ret = tmpfs_foreach(next, tmpfs_free_callout, NULL);
			if (ret < 0) {
				return -ECANCELED;
			}
		}

		/* Perform the callout */

		ret = callout(tdo, index, arg);
		switch (ret) {
		case TMPFS_CONTINUE:    /* Continue enumeration */
			/* Release the object and index to the next entry */

			tmpfs_release_lockedobject(to);
			index++;
			break;

		case TMPFS_HALT:        /* Stop enumeration */
			/* Release the object and cancel the traversal */

			tmpfs_release_lockedobject(to);
			return -ECANCELED;

		case TMPFS_UNLINKED:    /* Only the directory entry was deleted */
			/* Release the object and continue with the same index */

			tmpfs_release_lockedobject(to);

		case TMPFS_DELETED:     /* Object and directory entry deleted */
			break;                /* Continue with the same index */
		}
	}

	return OK;
}

/****************************************************************************
 * Name: tmpfs_open
 ****************************************************************************/

static int tmpfs_open(FAR struct file *filep, FAR const char *relpath,
		int oflags, mode_t mode)
{
	FAR struct inode *inode;
	FAR struct tmpfs_s *fs;
	FAR struct tmpfs_file_s *tfo;
	off_t offset;
	int ret;

	fvdbg("filep: %p\n", filep);
	DEBUGASSERT(filep->f_priv == NULL && filep->f_inode != NULL);

	/* Get the mountpoint inode reference from the file structure and the
	 * mountpoint private data from the inode structure
	 */

	inode = filep->f_inode;
	fs    = inode->i_private;

	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Skip over any leading directory separators (shouldn't be any) */

	for (; *relpath == '/'; relpath++);

	/* Find the file object associated with this relative path.
	 * If successful, this action will lock both the parent directory and
	 * the file object, adding one to the reference count of both.
	 * In the event that -ENOENT, there will still be a reference and
	 * lock on the returned directory.
	 */

	ret = tmpfs_find_file(fs, relpath, &tfo, NULL);
	if (ret >= 0) {
		/* The file exists.  We hold the lock and one reference count
		 * on the file object.
		 *
		 * It would be an error if we are asked to create it exclusively
		 */

		if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) {
			/* Already exists -- can't create it exclusively */

			ret = -EEXIST;
			goto errout_with_filelock;
		}

		/* Check if the caller has sufficient privileges to open the file */
		/* REVISIT: No file protection implemented */

		/* If O_TRUNC is specified and the file is opened for writing,
		 * then truncate the file.  This operation requires that the file is
		 * writeable, but we have already checked that. O_TRUNC without write
		 * access is ignored.
		 */

		if ((oflags & (O_TRUNC | O_WRONLY)) == (O_TRUNC | O_WRONLY)) {
			/* Truncate the file to zero length (if it is not already
			 * zero length)
			 */

			if (tfo->tfo_size > 0) {
				ret = tmpfs_realloc_file(&tfo, 0);
				if (ret < 0)
					goto errout_with_filelock;
			}
		}
	}

	/* ENOENT would be returned by tmpfs_find_file() if the full directory
	 * path was found, but the file was not found in the final directory.
	 */

	else if (ret == -ENOENT) {
		/* The file does not exist.  Were we asked to create it? */

		if ((oflags & O_CREAT) == 0) {
			/* No.. then we fail with -ENOENT */

			ret = -ENOENT;
			goto errout_with_fslock;
		}

		/* Yes.. create the file object.  There will be a reference and a lock
		 * on the new file object.
		 */

		ret = tmpfs_create_file(fs, relpath, &tfo);
		if (ret < 0) {
			goto errout_with_fslock;
		}
	}

	/* Some other error occurred */

	else {
		goto errout_with_fslock;
	}

	/* Save the struct tmpfs_file_s instance as the file private data */

	filep->f_priv = tfo;

	/* In write/append mode, we need to set the file pointer to the end of the
	 * file.
	 */

	offset = 0;
	if ((oflags & (O_APPEND | O_WRONLY)) == (O_APPEND | O_WRONLY))
		offset = tfo->tfo_size;

	filep->f_pos = offset;

	/* Unlock the file file object, but retain the reference count */

	tmpfs_unlock_file(tfo);
	tmpfs_unlock(fs);
	return OK;

	/* Error exits */

errout_with_filelock:
	tmpfs_release_lockedfile(tfo);

errout_with_fslock:
	tmpfs_unlock(fs);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_close
 ****************************************************************************/

static int tmpfs_close(FAR struct file *filep)
{
	FAR struct tmpfs_file_s *tfo;

	fvdbg("filep: %p\n", filep);
	DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);

	/* Recover our private data from the struct file instance */

	tfo = filep->f_priv;

	/* Get exclusive access to the file */

	tmpfs_lock_file(tfo);

	/* Decrement the reference count on the file */

	DEBUGASSERT(tfo->tfo_refs > 0);
	if (tfo->tfo_refs > 0) {
		tfo->tfo_refs--;
	}
	filep->f_priv = NULL;

	/* If the reference count decremented to zero and the file has been
	 * unlinked, then free the file allocation now.
	 */

	if (tfo->tfo_refs == 0 && (tfo->tfo_flags & TFO_FLAG_UNLINKED) != 0) {
		/* Free the file object while we hold the lock?  Weird but this
		 * should be safe because the object is unlinked and could not
		 * have any other references.
		 */

		kmm_free(tfo);
		return OK;
	}

	/* Release the lock on the file */

	tmpfs_unlock_file(tfo);
	return OK;
}

/****************************************************************************
 * Name: tmpfs_read
 ****************************************************************************/

static ssize_t tmpfs_read(FAR struct file *filep, FAR char *buffer,
		size_t buflen)
{
	FAR struct tmpfs_file_s *tfo;
	ssize_t nread;
	off_t startpos;
	off_t endpos;

	fvdbg("filep: %p buffer: %p buflen: %lu\n",
			filep, buffer, (unsigned long)buflen);
	DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);

	/* Recover our private data from the struct file instance */

	tfo = filep->f_priv;

	/* Get exclusive access to the file */

	tmpfs_lock_file(tfo);

	/* Handle attempts to read beyond the end of the file. */

	startpos = filep->f_pos;
	nread    = buflen;
	endpos   = startpos + buflen;

	if (endpos > tfo->tfo_size) {
		endpos = tfo->tfo_size;
		nread  = endpos - startpos;
	}

	/* Copy data from the memory object to the user buffer */

	memcpy(buffer, &tfo->tfo_data[startpos], nread);
	filep->f_pos += nread;

	/* Release the lock on the file */

	tmpfs_unlock_file(tfo);
	return nread;
}

/****************************************************************************
 * Name: tmpfs_write
 ****************************************************************************/

static ssize_t tmpfs_write(FAR struct file *filep, FAR const char *buffer,
		size_t buflen)
{
	FAR struct tmpfs_file_s *tfo;
	ssize_t nwritten;
	off_t startpos;
	off_t endpos;
	int ret;

	fvdbg("filep: %p buffer: %p buflen: %lu\n",
			filep, buffer, (unsigned long)buflen);
	DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);

	/* Recover our private data from the struct file instance */

	tfo = filep->f_priv;

	/* Get exclusive access to the file */

	tmpfs_lock_file(tfo);

	/* Handle attempts to read beyond the end of the file */

	startpos = filep->f_pos;
	nwritten = buflen;
	endpos   = startpos + buflen;

	if (endpos > tfo->tfo_size) {
		/* Reallocate the file to handle the write past the end of the file. */

		ret = tmpfs_realloc_file(&tfo, (size_t)endpos);
		if (ret < 0) {
			goto errout_with_lock;
		}
		filep->f_priv = tfo;
	}

	/* Copy data from the memory object to the user buffer */

	memcpy(&tfo->tfo_data[startpos], buffer, nwritten);
	filep->f_pos += nwritten;

	/* Release the lock on the file */

	tmpfs_unlock_file(tfo);
	return nwritten;

errout_with_lock:
	tmpfs_unlock_file(tfo);
	return (ssize_t)ret;
}

/****************************************************************************
 * Name: tmpfs_seek
 ****************************************************************************/

static off_t tmpfs_seek(FAR struct file *filep, off_t offset, int whence)
{
	FAR struct tmpfs_file_s *tfo;
	off_t position;

	fvdbg("filep: %p\n", filep);
	DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);

	/* Recover our private data from the struct file instance */

	tfo = filep->f_priv;

	/* Map the offset according to the whence option */

	switch (whence) {
	case SEEK_SET: /* The offset is set to offset bytes. */
		position = offset;
		break;

	case SEEK_CUR: /* The offset is set to its current location plus
					* offset bytes. */
		position = offset + filep->f_pos;
		break;

	case SEEK_END: /* The offset is set to the size of the file plus
					* offset bytes. */
		position = offset + tfo->tfo_size;
		break;

	default:
		return -EINVAL;
	}

	/* Attempts to set the position beyound the end of file will
	 * work if the file is open for write access.
	 *
	 * REVISIT: This simple implementation has no per-open storage that
	 * would be needed to retain the open flags.
	 */

#if 0
	if (position > tfo->tfo_size && (tfo->tfo_oflags & O_WROK) == 0) {
		/* Otherwise, the position is limited to the file size */

		position = tfo->tfo_size;
	}
#endif

	/* Save the new file position */

	filep->f_pos = position;
	return position;
}

/****************************************************************************
 * Name: tmpfs_ioctl
 ****************************************************************************/

static int tmpfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
	FAR struct tmpfs_file_s *tfo;
	FAR void **ppv = (FAR void**)arg;

	fvdbg("filep: %p cmd: %d arg: %08lx\n", filep, cmd, arg);
	DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);

	/* Recover our private data from the struct file instance */

	tfo = filep->f_priv;

	DEBUGASSERT(tfo != NULL);

	/* Only one ioctl command is supported */

	if (cmd == FIOC_MMAP && ppv != NULL) {
		/* Return the address on the media corresponding to the start of
		 * the file.
		 */

		*ppv = (FAR void *)tfo->tfo_data;
		return OK;
	}

	fdbg("ERROR: Invalid cmd: %d\n", cmd);
	return -ENOTTY;
}

/****************************************************************************
 * Name: tmpfs_dup
 ****************************************************************************/

static int tmpfs_dup(FAR const struct file *oldp, FAR struct file *newp)
{
	FAR struct tmpfs_file_s *tfo;

	fvdbg("Dup %p->%p\n", oldp, newp);
	DEBUGASSERT(oldp->f_priv != NULL && oldp->f_inode != NULL &&
			newp->f_priv == NULL && newp->f_inode != NULL);

	/* Recover our private data from the struct file instance */

	tfo = oldp->f_priv;
	DEBUGASSERT(tfo != NULL);

	/* Increment the reference count (atomically)*/

	tmpfs_lock_file(tfo);
	tfo->tfo_refs++;
	tmpfs_unlock_file(tfo);

	/* Save a copy of the file object as the dup'ed file.  This
	 * simple implementation does not many any per-open data
	 * structures so there is not really much to the dup operation.
	 */

	newp->f_priv = tfo;
	return OK;
}

/****************************************************************************
 * Name: tmpfs_fstat
 *
 * Description:
 *   Obtain information about an open file associated with the file
 *   descriptor 'fd', and will write it to the area pointed to by 'buf'.
 *
 ****************************************************************************/

static int tmpfs_fstat(FAR const struct file *filep, FAR struct stat *buf)
{
	FAR struct tmpfs_file_s *tfo;

	fvdbg("Fstat %p\n", buf);
	DEBUGASSERT(filep != NULL && buf != NULL);

	/* Recover our private data from the struct file instance */

	DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
	tfo = filep->f_priv;

	/* Get exclusive access to the file */

	tmpfs_lock_file(tfo);

	/* Return information about the file in the stat buffer.*/

	tmpfs_stat_common((FAR struct tmpfs_object_s *)tfo, buf);

	/* Release the lock on the file and return success. */

	tmpfs_unlock_file(tfo);
	return OK;
}

/****************************************************************************
 * Name: tmpfs_truncate
 ****************************************************************************/

static int tmpfs_truncate(FAR struct file *filep, off_t length)
{
	FAR struct tmpfs_file_s *tfo;
	size_t oldsize;
	int ret = OK;

	fvdbg("filep: %p length: %ld\n", filep, (long)length);
	DEBUGASSERT(filep != NULL && length >= 0);

	/* Recover our private data from the struct file instance */

	tfo = filep->f_priv;

	/* Get exclusive access to the file */

	tmpfs_lock_file(tfo);

	/* Get the old size of the file.  Do nothing if the file size is not
	 * changing.
	 */

	oldsize = tfo->tfo_size;
	if (oldsize != length) {
		/* The size is changing.. up or down.  Reallocate the file memory. */
		ret = tmpfs_realloc_file(&tfo, (size_t)length);
		if (ret < 0) {
			goto errout_with_lock;
		}

		filep->f_priv = tfo;

		/* If the size has increased, then we need to zero the newly added
		 * memory.
		 */

		if (length > oldsize) {
		  memset(&tfo->tfo_data[oldsize], 0, length - oldsize);
		}
		ret = OK;
	}

	/* Release the lock on the file */
errout_with_lock:
	tmpfs_unlock_file(tfo);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_opendir
 ****************************************************************************/

static int tmpfs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
		FAR struct fs_dirent_s *dir)
{
	FAR struct tmpfs_s *fs;
	FAR struct tmpfs_directory_s *tdo;
	int ret;

	fvdbg("mountpt: %p relpath: %s dir: %p\n", mountpt, relpath, dir);
	DEBUGASSERT(mountpt != NULL && relpath != NULL && dir != NULL);

	/* Get the mountpoint private data from the inode structure */

	fs = mountpt->i_private;
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Skip over any leading directory separators (shouldn't be any) */

	for (; *relpath == '/'; relpath++);

	/* Find the directory object associated with this relative path.
	 * If successful, this action will lock both the parent directory and
	 * the file object, adding one to the reference count of both.
	 * In the event that -ENOENT, there will still be a reference and
	 * lock on the returned directory.
	 */

	ret = tmpfs_find_directory(fs, relpath, &tdo, NULL);
	if (ret >= 0) {
		dir->u.tmpfs.tf_tdo   = tdo;
		dir->u.tmpfs.tf_index = 0;

		tmpfs_unlock_directory(tdo);
	}

	/* Release the lock on the file system and return the result */

	tmpfs_unlock(fs);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_closedir
 ****************************************************************************/

static int tmpfs_closedir(FAR struct inode *mountpt,
		FAR struct fs_dirent_s *dir)
{
	FAR struct tmpfs_directory_s *tdo;

	fvdbg("mountpt: %p dir: %p\n",  mountpt, dir);
	DEBUGASSERT(mountpt != NULL && dir != NULL);

	/* Get the directory structure from the dir argument */

	tdo = dir->u.tmpfs.tf_tdo;
	DEBUGASSERT(tdo != NULL);

	/* Decrement the reference count on the directory object */

	tmpfs_lock_directory(tdo);
	tdo->tdo_refs--;
	tmpfs_unlock_directory(tdo);
	return OK;
}

/****************************************************************************
 * Name: tmpfs_readdir
 ****************************************************************************/

static int tmpfs_readdir(FAR struct inode *mountpt,
		FAR struct fs_dirent_s *dir)
{
	FAR struct tmpfs_directory_s *tdo;
	unsigned int index;
	int ret;

	fvdbg("mountpt: %p dir: %p\n",  mountpt, dir);
	DEBUGASSERT(mountpt != NULL && dir != NULL);

	/* Get the directory structure from the dir argument and lock it */

	tdo = dir->u.tmpfs.tf_tdo;
	DEBUGASSERT(tdo != NULL);

	tmpfs_lock_directory(tdo);

	/* Have we reached the end of the directory? */

	index = dir->u.tmpfs.tf_index;
	if (index >= tdo->tdo_nentries) {
		/* We signal the end of the directory by returning the special error:
		 * -ENOENT
		 */

		fvdbg("End of directory\n");
		ret = -ENOENT;
	} else {

		FAR struct tmpfs_dirent_s *tde;
		FAR struct tmpfs_object_s *to;

		/* Does this entry refer to a file or a directory object? */

		tde = &tdo->tdo_entry[index];
		to  = tde->tde_object;
		DEBUGASSERT(to != NULL);

		if (to->to_type == TMPFS_DIRECTORY) {
			/* A directory */

			dir->fd_dir.d_type = DTYPE_DIRECTORY;
		} else /* to->to_type == TMPFS_REGULAR) */ {

			/* A regular file */

			dir->fd_dir.d_type = DTYPE_FILE;
		}

		/* Copy the entry name */

		strncpy(dir->fd_dir.d_name, tde->tde_name, NAME_MAX + 1);

		/* Increment the index for next time */

		dir->u.tmpfs.tf_index = index + 1;
		ret = OK;
	}

	tmpfs_unlock_directory(tdo);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_rewinddir
 ****************************************************************************/

static int tmpfs_rewinddir(FAR struct inode *mountpt,
		FAR struct fs_dirent_s *dir)
{
	fvdbg("mountpt: %p dir: %p\n",  mountpt, dir);
	DEBUGASSERT(mountpt != NULL && dir != NULL);

	/* Set the readdir index to zero */

	dir->u.tmpfs.tf_index = 0;
	return OK;
}

/****************************************************************************
 * Name: tmpfs_bind
 ****************************************************************************/

static int tmpfs_bind(FAR struct inode *blkdriver, FAR const void *data,
		FAR void **handle)
{
	FAR struct tmpfs_directory_s *tdo;
	FAR struct tmpfs_s *fs;

	fvdbg("blkdriver: %p data: %p handle: %p\n", blkdriver, data, handle);
	DEBUGASSERT(blkdriver == NULL && handle != NULL);

	/* Create an instance of the tmpfs file system */

	fs = (FAR struct tmpfs_s *)kmm_zalloc(sizeof(struct tmpfs_s));
	if (fs == NULL) {
		return -ENOMEM;
	}
	/* Create a root file system.  This is like a single directory entry in
	 * the file system structure.
	 */

	tdo = tmpfs_alloc_directory();
	if (tdo == NULL) {
		kmm_free(fs);
		return -ENOMEM;
	}

	fs->tfs_root.tde_object = (FAR struct tmpfs_object_s *)tdo;
	fs->tfs_root.tde_name   = "";

	/* Set up the backward link (to support reallocation) */

	tdo->tdo_dirent         = &fs->tfs_root;

	/* Initialize the file system state */

	fs->tfs_exclsem.ts_holder = TMPFS_NO_HOLDER;
	fs->tfs_exclsem.ts_count  = 0;
	sem_init(&fs->tfs_exclsem.ts_sem, 0, 1);

	/* Return the new file system handle */

	*handle = (FAR void *)fs;
	return OK;
}

/****************************************************************************
 * Name: tmpfs_unbind
 ****************************************************************************/
static int tmpfs_unbind(FAR void *handle, FAR struct inode **blkdriver)
{
	FAR struct tmpfs_s *fs = (FAR struct tmpfs_s *)handle;
	FAR struct tmpfs_directory_s *tdo;
	int ret;

	fvdbg("handle: %p blkdriver: %p\n", handle, blkdriver);
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Lock the file system */

	tmpfs_lock(fs);

	/* Traverse all directory entries (recursively), freeing all resources. */

	tdo = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
	ret = tmpfs_foreach(tdo, tmpfs_free_callout, NULL);

	/* Now we can destroy the root file system and the file system itself. */

	sem_destroy(&tdo->tdo_exclsem.ts_sem);
	kmm_free(tdo);

	sem_destroy(&fs->tfs_exclsem.ts_sem);
	kmm_free(fs);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_statfs
 ****************************************************************************/

static int tmpfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf)
{
	FAR struct tmpfs_s *fs;
	FAR struct tmpfs_directory_s *tdo;
	struct tmpfs_statfs_s tmpbuf;
	size_t inuse;
	size_t avail;
	off_t blkalloc;
	off_t blkused;
	int ret;

	fvdbg("mountpt: %p buf: %p\n", mountpt, buf);
	DEBUGASSERT(mountpt != NULL && buf != NULL);

	/* Get the file system structure from the inode reference. */

	fs = mountpt->i_private;
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Set up the memory use for the file system and root directory object */

	tdo = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;
	inuse = sizeof(struct tmpfs_s) +
		SIZEOF_TMPFS_DIRECTORY(tdo->tdo_nentries);
	avail = sizeof(struct tmpfs_s) + tdo->tdo_alloc - inuse;

	tmpbuf.tsf_alloc = tdo->tdo_alloc;
	tmpbuf.tsf_inuse = inuse;
	tmpbuf.tsf_files = 0;
	tmpbuf.tsf_ffree = avail / sizeof(struct tmpfs_dirent_s);

	/* Traverse the file system to accurmulate statistics */

	ret = tmpfs_foreach(tdo, tmpfs_statfs_callout, (FAR void *)&tmpbuf);
	if (ret < 0) {
		return -ECANCELED;
	}
	/* Return something for the file system description */

	blkalloc        = (tmpbuf.tsf_alloc + CONFIG_FS_TMPFS_BLOCKSIZE - 1) /
		CONFIG_FS_TMPFS_BLOCKSIZE;
	blkused         = (tmpbuf.tsf_inuse + CONFIG_FS_TMPFS_BLOCKSIZE - 1) /
		CONFIG_FS_TMPFS_BLOCKSIZE;

	buf->f_type     = TMPFS_MAGIC;
	buf->f_namelen  = NAME_MAX;
	buf->f_bsize    = CONFIG_FS_TMPFS_BLOCKSIZE;
	buf->f_blocks   = blkalloc;
	buf->f_bfree    = blkalloc - blkused;
	buf->f_bavail   = blkalloc - blkused;
	buf->f_files    = tmpbuf.tsf_files;
	buf->f_ffree    = tmpbuf.tsf_ffree;

	/* Release the lock on the file system */

	tmpfs_unlock(fs);
	return OK;
}

/****************************************************************************
 * Name: tmpfs_unlink
 ****************************************************************************/

static int tmpfs_unlink(FAR struct inode *mountpt, FAR const char *relpath)
{
	FAR struct tmpfs_s *fs;
	FAR struct tmpfs_directory_s *tdo;
	FAR struct tmpfs_file_s *tfo = NULL;
	FAR const char *name;
	int ret;

	fvdbg("mountpt: %p relpath: %s\n", mountpt, relpath);
	DEBUGASSERT(mountpt != NULL && relpath != NULL);

	/* Get the file system structure from the inode reference. */

	fs = mountpt->i_private;
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Find the file object and parent directory associated with this relative
	 * path.  If successful, tmpfs_find_file will lock both the file object
	 * and the parent directory and take one reference count on each.
	 */

	ret = tmpfs_find_file(fs, relpath, &tfo, &tdo);
	if (ret < 0) {
		goto errout_with_lock;
	}
	DEBUGASSERT(tfo != NULL);

	/* Get the file name from the relative path */

	name = strrchr(relpath, '/');
	if (name != NULL) {
		/* Skip over the file '/' character */

		name++;
	} else {
		/* The name must lie in the root directory */

		name = relpath;
	}

	/* Remove the file from parent directory */

	ret = tmpfs_remove_dirent(tdo, name);
	if (ret < 0)
		goto errout_with_objects;


	/* If the reference count is not one, then just mark the file as
	 * unlinked
	 */

	if (tfo->tfo_refs > 1) {
		/* Make the file object as unlinked */

		tfo->tfo_flags |= TFO_FLAG_UNLINKED;

		/* Release the reference count on the file object */

		tfo->tfo_refs--;
		tmpfs_unlock_file(tfo);
	}

	/* Otherwise we can free the object now */

	else {
		sem_destroy(&tfo->tfo_exclsem.ts_sem);
		kmm_free(tfo);
	}

	/* Release the reference and lock on the parent directory */

	tdo->tdo_refs--;
	tmpfs_unlock_directory(tdo);
	tmpfs_unlock(fs);

	return OK;

errout_with_objects:
	tmpfs_release_lockedfile(tfo);

	tdo->tdo_refs--;
	tmpfs_unlock_directory(tdo);

errout_with_lock:
	tmpfs_unlock(fs);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_mkdir
 ****************************************************************************/

static int tmpfs_mkdir(FAR struct inode *mountpt, FAR const char *relpath,
		mode_t mode)
{
	FAR struct tmpfs_s *fs;
	int ret;

	fvdbg("mountpt: %p relpath: %s mode: %04x\n", mountpt, relpath, mode);
	DEBUGASSERT(mountpt != NULL && relpath != NULL);

	/* Get the file system structure from the inode reference. */

	fs = mountpt->i_private;
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Create the directory. */

	ret = tmpfs_create_directory(fs, relpath, NULL);
	tmpfs_unlock(fs);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_rmdir
 ****************************************************************************/

static int tmpfs_rmdir(FAR struct inode *mountpt, FAR const char *relpath)
{
	FAR struct tmpfs_s *fs;
	FAR struct tmpfs_directory_s *parent;
	FAR struct tmpfs_directory_s *tdo;
	FAR const char *name;
	int ret;

	fvdbg("mountpt: %p relpath: %s\n", mountpt, relpath);
	DEBUGASSERT(mountpt != NULL && relpath != NULL);

	/* Get the file system structure from the inode reference. */

	fs = mountpt->i_private;
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Find the directory object and parent directory associated with this
	 * relative path.  If successful, tmpfs_find_file will lock both the
	 * directory object and the parent directory and take one reference count
	 * on each.
	 */

	ret = tmpfs_find_directory(fs, relpath, &tdo, &parent);
	if (ret < 0) {
		goto errout_with_lock;
	}
	/* Is the directory empty?  We cannot remove directories that still
	 * contain references to file system objects.  No can we remove the
	 * directory if there are outstanding references on it (other than
	 * our reference).
	 */

	if (tdo->tdo_nentries > 0 || tdo->tdo_refs > 1) {
		ret = -EBUSY;
		goto errout_with_objects;
	}

	/* Get the directory name from the relative path */

	name = strrchr(relpath, '/');
	if (name != NULL) {
		/* Skip over the fidirectoryle '/' character */

		name++;
	} else {
		/* The name must lie in the root directory */

		name = relpath;
	}

	/* Remove the directory from parent directory */

	ret = tmpfs_remove_dirent(parent, name);
	if (ret < 0) {
		goto errout_with_objects;
	}
	/* Free the directory object */

	sem_destroy(&tdo->tdo_exclsem.ts_sem);
	kmm_free(tdo);

	/* Release the reference and lock on the parent directory */

	parent->tdo_refs--;
	tmpfs_unlock_directory(parent);
	tmpfs_unlock(fs);

	return OK;

errout_with_objects:
	tdo->tdo_refs--;
	tmpfs_unlock_directory(tdo);

	parent->tdo_refs--;
	tmpfs_unlock_directory(parent);

errout_with_lock:
	tmpfs_unlock(fs);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_rename
 ****************************************************************************/

static int tmpfs_rename(FAR struct inode *mountpt, FAR const char *oldrelpath,
		FAR const char *newrelpath)
{
	FAR struct tmpfs_directory_s *oldparent;
	FAR struct tmpfs_directory_s *newparent;
	FAR struct tmpfs_object_s *to;
	FAR struct tmpfs_s *fs;
	FAR const char *oldname;
	FAR char *newname;
	FAR char *copy;
	int ret;

	fvdbg("mountpt: %p oldrelpath: %s newrelpath: %s\n",
			mountpt, oldrelpath, newrelpath);
	DEBUGASSERT(mountpt != NULL && oldrelpath != NULL && newrelpath != NULL);

	/* Get the file system structure from the inode reference. */

	fs = mountpt->i_private;
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Duplicate the newpath variable so that we can modify it */

	copy = strdup(newrelpath);
	if (copy == NULL) {
		return -ENOMEM;
	}
	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Separate the new path into the new file name and the path to the new
	 * parent directory.
	 */

	newname = strrchr(copy, '/');
	if (newname == NULL) {
		/* No subdirectories... use the root directory */

		newname   = copy;
		newparent = (FAR struct tmpfs_directory_s *)fs->tfs_root.tde_object;

		tmpfs_lock_directory(newparent);
		newparent->tdo_refs++;
	} else {
		/* Terminate the parent directory path */

		*newname++ = '\0';

		/* Locate the parent directory that should contain this name.
		 * On success, tmpfs_find_directory() will lockthe parent
		 * directory and increment the reference count.
		 */

		ret = tmpfs_find_directory(fs, copy, &newparent, NULL);
		if (ret < 0) {
			goto errout_with_lock;
		}
	}

	/* Verify that no object of this name already exists in the destination
	 * directory.
	 */

	ret = tmpfs_find_dirent(newparent, newname);
	if (ret != -ENOENT) {
		/* Something with this name already exists in the directory.
		 * OR perhaps some fatal error occurred.
		 */

		if (ret >= 0) {
			ret = -EEXIST;
		}
		goto errout_with_newparent;
	}

	/* Find the old object at oldpath.  If successful, tmpfs_find_object()
	 * will lock both the object and the parent directory and will increment
	 * the reference count on both.
	 */

	ret = tmpfs_find_object(fs, oldrelpath, &to, &oldparent);
	if (ret < 0) {
		goto errout_with_newparent;
	}
	/* Get the old file name from the relative path */

	oldname = strrchr(oldrelpath, '/');
	if (oldname != NULL) {
		/* Skip over the file '/' character */

		oldname++;
	} else {
		/* The name must lie in the root directory */

		oldname = oldrelpath;
	}

	/* Remove the entry from the parent directory */

	ret = tmpfs_remove_dirent(oldparent, oldname);
	if (ret < 0) {
		goto errout_with_oldparent;
	}
	/* Add an entry to the new parent directory. */

	ret = tmpfs_add_dirent(&newparent, to, newname);

errout_with_oldparent:
	oldparent->tdo_refs--;
	tmpfs_unlock_directory(oldparent);

	tmpfs_release_lockedobject(to);

errout_with_newparent:
	newparent->tdo_refs--;
	tmpfs_unlock_directory(newparent);

errout_with_lock:
	tmpfs_unlock(fs);
	kmm_free(copy);
	return ret;
}

/****************************************************************************
 * Name: tmpfs_stat_common
 ****************************************************************************/

static void tmpfs_stat_common(FAR struct tmpfs_object_s *to,
		FAR struct stat *buf)
{
	size_t objsize;

	/* Is the tmpfs object a regular file? */

	memset(buf, 0, sizeof(struct stat));

	if (to->to_type == TMPFS_REGULAR) {
		FAR struct tmpfs_file_s *tfo =
			(FAR struct tmpfs_file_s *)to;

		/* -rwxrwxrwx */

		buf->st_mode = S_IRWXO | S_IRWXG | S_IRWXU | S_IFREG;

		/* Get the size of the object */

		objsize = tfo->tfo_size;
	} else /* if (to->to_type == TMPFS_DIRECTORY) */ {
		FAR struct tmpfs_directory_s *tdo =
			(FAR struct tmpfs_directory_s *)to;

		/* drwxrwxrwx */

		buf->st_mode = S_IRWXO | S_IRWXG | S_IRWXU | S_IFDIR;

		/* Get the size of the object */

		objsize = SIZEOF_TMPFS_DIRECTORY(tdo->tdo_nentries);
	}

	/* Fake the rest of the information */

	buf->st_size    = objsize;
	buf->st_blksize = CONFIG_FS_TMPFS_BLOCKSIZE;
	buf->st_blocks  = (objsize + CONFIG_FS_TMPFS_BLOCKSIZE - 1) /
		CONFIG_FS_TMPFS_BLOCKSIZE;
}

/****************************************************************************
 * Name: tmpfs_stat
 ****************************************************************************/

static int tmpfs_stat(FAR struct inode *mountpt, FAR const char *relpath,
		FAR struct stat *buf)
{
	FAR struct tmpfs_s *fs;
	FAR struct tmpfs_object_s *to;
	int ret;

	fvdbg("mountpt=%p relpath=%s buf=%p\n", mountpt, relpath, buf);
	DEBUGASSERT(mountpt != NULL && relpath != NULL && buf != NULL);

	/* Get the file system structure from the inode reference. */

	fs = mountpt->i_private;
	DEBUGASSERT(fs != NULL && fs->tfs_root.tde_object != NULL);

	/* Get exclusive access to the file system */

	tmpfs_lock(fs);

	/* Find the tmpfs object at the relpath.  If successful,
	 * tmpfs_find_object() will lock the object and increment the
	 * reference count on the object.
	 */

	ret = tmpfs_find_object(fs, relpath, &to, NULL);
	if (ret < 0) {
		goto errout_with_fslock;
	}
	/* We found it... Return information about the file object in the stat
	 * buffer.
	 */

	DEBUGASSERT(to != NULL);
	tmpfs_stat_common(to, buf);

	/* Unlock the object and return success */

	tmpfs_release_lockedobject(to);
	ret = OK;

errout_with_fslock:
	tmpfs_unlock(fs);
	return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

